﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numerics;
using System.Security.Cryptography;

namespace BitcoinLibrary
{
/**
 * Represents an elliptic curve keypair that we own and can use for signing transactions. Currently,
 * Bouncy Castle is used. In future this may become an interface with multiple implementations using different crypto
 * libraries. The class also provides a static method that can verify a signature with just the public key.<p>
 */
public class ECKey /*implements Serializable */
{
    //private static readonly ECDomainParameters ecParams;

    //private static readonly SecureRandom secureRandom;
    private const long serialVersionUID = -728224901792295832L;
    private static RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
    //private ECDsaCng generator = new ECDsaCng();

    ////static {
    //    // All clients must agree on the curve to use by agreement. BitCoin uses secp256k1.
    //private static X9ECParameters params = SECNamedCurves.getByName("secp256k1");
    //    private static ecParams = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(),  params.getH());
    //    private static secureRandom = new SecureRandom();
    ////}

    private readonly BigInteger priv;
    private readonly byte[] pub;

    private byte[] pubKeyHash;

    /** Generates an entirely new keypair. */
    public ECKey() 
    {
        ECDsaCng generator = new ECDsaCng(256);
        generator.HashAlgorithm = CngAlgorithm.ECDsaP256;
        priv = new BigInteger(generator.Key.Export(CngKeyBlobFormat.EccPublicBlob));
        pub = generator.Key.Export(CngKeyBlobFormat.EccPublicBlob);
        //char[] outArray = new char[200];
        //int ret = Convert.ToBase64CharArray(pub, 0, pub.Length, outArray, 0);
        //string str = Convert.ToBase64String(pub);
        //generator.KeySize = generator.LegalKeySizes
        //ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(ecParams, secureRandom);
        //generator.init(keygenParams);
        //AsymmetricCipherKeyPair keypair = generator.generateKeyPair();
        //ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
        //ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
        //priv = privParams.getD();
        //// The public key is an encoded point on the elliptic curve. It has no meaning independent of the curve.
        //pub = pubParams.getQ().getEncoded();
    }

    /**
     * Construct an ECKey from an ASN.1 encoded private key. These are produced by OpenSSL and stored by the BitCoin
     * reference implementation in its wallet.
     */
    public static ECKey fromASN1(byte[] asn1privkey) 
    {
        throw new Exception("NYI");
        //return new ECKey(extractPrivateKeyFromASN1(asn1privkey));
    }

    /**
     * Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by the BitCoin reference
     * implementation in its wallet storage format.
     */
    public byte[] toASN1()
    {
        throw new Exception("NYI");
         //try {
         //    ByteArrayOutputStream baos = new ByteArrayOutputStream(400);
         //    ASN1OutputStream encoder = new ASN1OutputStream(baos);

         //    // ASN1_SEQUENCE(EC_PRIVATEKEY) = {
         //    //   ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG),
         //    //   ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING),
         //    //   ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0),
         //    //   ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1)
         //    // } ASN1_SEQUENCE_END(EC_PRIVATEKEY)
         //    DERSequenceGenerator seq = new DERSequenceGenerator(encoder);
         //    seq.addObject(new DERInteger(1)); // version
         //    seq.addObject(new DEROctetString(priv.toByteArray()));
         //    seq.addObject(new DERTaggedObject(0, SECNamedCurves.getByName("secp256k1").getDERObject()));
         //    seq.addObject(new DERTaggedObject(1, new DERBitString(getPubKey())));
         //    seq.close();
         //    encoder.close();
         //    return baos.toByteArray();
         //} catch (IOException e) {
         //    throw new RuntimeException(e);  // Cannot happen, writing to memory stream.
         //}
    }

    /**
     * Creates an ECKey given only the private key. This works because EC public keys are derivable from their
     * private keys by doing a multiply with the generator value.
     */
    public ECKey(BigInteger privKey) 
    {
        this.priv = privKey;
        this.pub = publicKeyFromPrivate(privKey);
    }

    /** Derive the public key by doing a point multiply of G * priv. */
    private static byte[] publicKeyFromPrivate(BigInteger privKey) 
    {
        throw new Exception("NYI");
        //return ecParams.getG().multiply(privKey).getEncoded();
    }

    /** Gets the hash160 form of the public key (as seen in addresses). */
    public byte[] getPubKeyHash() 
    {
        if (pubKeyHash == null)
        {
            pubKeyHash = Utils.sha256hash160(this.pub);
        }
        return pubKeyHash;
    }

    /**
     * Gets the raw public key value. This appears in transaction scriptSigs. Note that this is <b>not</b> the same
     * as the pubKeyHash/address.
     */
    public byte[] getPubKey() 
    {
        return pub;
    }

    public override string  ToString()
{
        StringBuilder b = new StringBuilder();
        b.Append("pub:").Append(Utils.bytesToHexString(pub));
        b.Append(" priv:").Append(Utils.bytesToHexString(priv.ToByteArray()));
        return b.ToString();
    }

    /**
     * Returns the address that corresponds to the public part of this ECKey. Note that an address is derived from
     * the RIPEMD-160 hash of the public key and is not the public key itself (which is too large to be convenient).
     */
    public Address toAddress(NetworkParameters param) 
{
        byte[] hash160 = Utils.sha256hash160(pub);
        return new Address(param, hash160);
    }

    /**
     * Calcuates an ECDSA signature in DER format for the given input hash. Note that the input is expected to be
     * 32 bytes long.
     */
    public byte[] sign(byte[] input) 
    {
        throw new Exception("NYI");
        //ECDSASigner signer = new ECDSASigner();
        //ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, ecParams);
        //signer.init(true, privKey);
        //BigInteger[] sigs = signer.generateSignature(input);
        //// What we get back from the signer are the two components of a signature, r and s. To get a flat byte stream
        //// of the type used by BitCoin we have to encode them using DER encoding, which is just a way to pack the two
        //// components into a structure.
        //try {
        //    ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //    DERSequenceGenerator seq = new DERSequenceGenerator(bos);
        //    seq.addObject(new DERInteger(sigs[0]));
        //    seq.addObject(new DERInteger(sigs[1]));
        //    seq.close();
        //    return bos.toByteArray();
        //} catch (IOException e) {
        //    throw new RuntimeException(e);  // Cannot happen.
        //}
    }

    /**
     * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
     * @param data Hash of the data to verify.
     * @param signature ASN.1 encoded signature.
     * @param pub The public key bytes to use.
     */
    public static bool verify(byte[] data, byte[] signature, byte[] pub) 
    {
        throw new Exception("NYI");
        //ECDSASigner signer = new ECDSASigner();
        //ECPublicKeyParameters params = new ECPublicKeyParameters(ecParams.getCurve().decodePoint(pub), ecParams);
        //signer.init(false, params);
        //try {
        //    ASN1InputStream decoder = new ASN1InputStream(signature);
        //    DERSequence seq = (DERSequence) decoder.readObject();
        //    DERInteger r = (DERInteger) seq.getObjectAt(0);
        //    DERInteger s = (DERInteger) seq.getObjectAt(1);
        //    decoder.close();
        //    return signer.verifySignature(data, r.getValue(), s.getValue());
        //} catch (IOException e) {
        //    throw new RuntimeException(e);
        //}
    }

    /**
     * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
     * @param data Hash of the data to verify.
     * @param signature ASN.1 encoded signature.
     */
    public bool verify(byte[] data, byte[] signature) 
    {
        return ECKey.verify(data, signature, pub);
    }

    private static BigInteger extractPrivateKeyFromASN1(byte[] asn1privkey) 
    {
        // To understand this code, see the definition of the ASN.1 format for EC private keys in the OpenSSL source
        // code in ec_asn1.c:
        //
        // ASN1_SEQUENCE(EC_PRIVATEKEY) = {
        //   ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG),
        //   ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING),
        //   ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0),
        //   ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1)
        // } ASN1_SEQUENCE_END(EC_PRIVATEKEY)
        //

        throw new Exception("NYI");
        //try {
        //    ASN1InputStream decoder = new ASN1InputStream(asn1privkey);
        //    DERSequence seq = (DERSequence) decoder.readObject();
        //    assert seq.size() == 4 : "Input does not appear to be an ASN.1 OpenSSL EC private key";
        //    assert ((DERInteger) seq.getObjectAt(0)).getValue().equals(BigInteger.ONE) : "Input is of wrong version";
        //    DEROctetString key = (DEROctetString) seq.getObjectAt(1);
        //    decoder.close();
        //    return new BigInteger(key.getOctets());
        //} catch (IOException e) {
        //    throw new RuntimeException(e);  // Cannot happen, reading from memory stream.
        //}
    }
}
}
